package retrofit.boot.annotation;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import retrofit.boot.util.RetrofitUtils;

import java.util.*;

/**
 * Created by wulg on 2019/11/5
 * description:
 */
public final class RetrofitClientClientsRegistrar extends RetrofitClientRegistrySupport implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableRetrofitClients.class.getName());
        final Class<?>[] excludeClients = attrs == null ? null : (Class<?>[]) attrs.get("exclude");
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.addIncludeFilter(new AnnotationTypeFilter(RetrofitClient.class));
        if(ArrayUtils.isNotEmpty(excludeClients)){
            for(Class<?> excludeClient : excludeClients){
                if(excludeClient.isInterface()){
                    scanner.addExcludeFilter(new AssignableTypeFilter(excludeClient));
                }
            }
        }
        Set<String> basePackages = getBasePackages(metadata);
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (!(candidateComponent instanceof AnnotatedBeanDefinition)) {
                    continue;
                }
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),"@RetrofitClient can only be specified on an interface");
                Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(RetrofitClient.class.getCanonicalName());
                RetrofitClientWrapper wrapper = new RetrofitClientWrapper(
                        annotationMetadata.getClassName(),
                        getName(attributes),
                        RetrofitUtils.rebuildUrl((String) attributes.get("url")),
                        (String) attributes.get("qualifier"),
                        (Boolean)attributes.get("primary"));
                registerRetrofitClients(registry,wrapper);//注册
            }
        }
    }

    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableRetrofitClients.class.getCanonicalName());
        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        if (basePackages.isEmpty()) {
            basePackages.add( ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }



    private String getName(Map<String, Object> attributes) {
        String name = (String) attributes.get("name");
        if (!StringUtils.hasText(name)) {
            name = (String) attributes.get("value");
        }
        name = resolve(name);
        if (!StringUtils.hasText(name)) {
            return "";
        }
        return name;
    }
}
